// impl trait can be use in two locations: 
//  1. as an argument type 
//  2. as a return type
//
//
//  as an argument type
trait Name{ 
    fn get_name(&self) -> &'static str;
}

struct Dog {
    name: &'static str
}

impl Name for Dog {
    fn get_name(&self) -> &'static str {
        self.name
    }
}
impl Dog {
    fn bark(&self) {
        println!("bark: {}", self.get_name()); 
    }
}

impl Drop for Dog {
    fn drop(&mut self) {
        println!("> Dropping {}", self.get_name());
    }
}

fn bark(named: &impl Name) {
    println!("bark: {}", named.get_name());
}
fn bark_generic<N: Name>(named: &N) {
    println!("bark_generic: {}", named.get_name());
}

fn impl_trait_as_arg(){
    let dog = Dog{ name: "john"};
    dog.bark();

    bark(&dog);
    bark_generic(&dog);
}


// as a return type
fn create_dog() -> impl Name {
    Dog{name: "greedy"} //Move to return value
}

fn impl_trait_as_return(){
    let n = create_dog();
    println!("name is : {}", n.get_name());
}

pub fn main(){
    impl_trait_as_arg();
    impl_trait_as_return();
}
